{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "\n",
    "\n",
    "在“手写数字识别”案例的快速入门中，我们调用飞桨提供的API（[paddle.dataset.mnist](https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/data/dataset_cn.html)）加载MNIST数据集。但在工业实践中，我们面临的任务和数据环境千差万别，需要编写适合当前任务的数据处理程序。\n",
    "\n",
    "但是编写自定义的数据加载函数，一般会涉及以下四个部分：\n",
    "\n",
    "- 数据读取与数据集划分\n",
    "-  定义数据读取器\n",
    "-  校验数据的有效性\n",
    "-  异步数据读取\n",
    "\n",
    "\n",
    "\n",
    "在数据读取与处理前，首先要加载飞桨平台和数据处理库，可能使用的库都需要加载进来：\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "#数据处理部分之前的代码，加入部分数据处理的库\n",
    "import paddle\n",
    "import paddle.fluid as fluid\n",
    "from paddle.fluid.dygraph.nn import FC\n",
    "import numpy as np\n",
    "import os\n",
    "import gzip\n",
    "import json\n",
    "import random"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# 1. 数据读取与数据集划分\n",
    "\n",
    "\n",
    "实际保存到的数据存储格式多种多样，本节使用的mnist数据集以json格式存储在本地。\n",
    "\n",
    "在'./work/'目录下读取文件名称为'mnist.json.gz'的MINST手写数字识别数据，文件格式是压缩后的json文件。文件内容包括：训练数据、验证数据、测试数据三部分，分别包含50000、10000、10000条手写数字数据和两个元素列表。\n",
    "\n",
    "以训练集数据为例，它为两个元素的列表为[traim_imgs, train_labels]。 \n",
    "* train_imgs：一个维度为[5000, 784]的二维列表，包含5000张图片。每张图片用一个长度为784的向量表示,内容是28*28尺寸的像素灰度值（黑白图片）。\n",
    "* train_labels：一个维度为[5000, ]的列表，表示这些图片对应的分类标签，即0-9之间的一个数字。接下来我们将数据读取出来。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loading mnist dataset from ./work/mnist.json.gz ......\n",
      "mnist dataset load done\n",
      "训练数据集数量:  50000\n",
      "验证数据集数量:  10000\n",
      "测试数据集数量:  10000\n"
     ]
    }
   ],
   "source": [
    "# 声明数据集文件位置\n",
    "datafile = './work/mnist.json.gz'\n",
    "print('loading mnist dataset from {} ......'.format(datafile))\n",
    "# 加载json数据文件\n",
    "data = json.load(gzip.open(datafile))\n",
    "print('mnist dataset load done')\n",
    "# 读取到的数据区分训练集，验证集，测试集\n",
    "train_set, val_set, eval_set = data\n",
    "\n",
    "# 数据集相关参数，图片高度IMG_ROWS, 图片宽度IMG_COLS\n",
    "IMG_ROWS = 28\n",
    "IMG_COLS = 28\n",
    "\n",
    "# 打印数据信息\n",
    "imgs, label = train_set[0], train_set[1]\n",
    "print(\"训练数据集数量: \", len(imgs))\n",
    "\n",
    "# 观察验证集数量\n",
    "imgs, label = val_set[0], val_set[1]\n",
    "print(\"验证数据集数量: \", len(imgs))\n",
    "\n",
    "# 观察测试集数量\n",
    "imgs, label = val= eval_set[0], eval_set[1]\n",
    "print(\"测试数据集数量: \", len(imgs))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "\n",
    "\n",
    "# 2. 定义数据读取函数\n",
    "\n",
    "飞桨提供分批次读取数据函数paddle.batch，该接口是一个reader的装饰器，返回的reader将输入的reader的数据打包成指定的batch_size大小的批处理数据（batched.data）\n",
    "\n",
    "在定义数据读取函数中，我们需要做很多事情，包括但不限于：\n",
    "- 打乱数据，保证每轮训练读取的数据顺序不同。\n",
    "- 数据类型转换。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练数据集数量:  50000\n"
     ]
    }
   ],
   "source": [
    "# 获得训练数据集\n",
    "imgs, label = train_set[0], train_set[1]\n",
    "print(\"训练数据集数量: \", len(imgs))\n",
    "# 获得训练数据集长度\n",
    "imgs_length = len(imgs)\n",
    "# 定义数据集每个数据的序号，根据序号读取数据\n",
    "index_list = list(range(imgs_length))\n",
    "# 读入数据时用到的批次大小\n",
    "BATCHSIZE = 100\n",
    "\n",
    "# 定义数据生成器\n",
    "def data_generator():\n",
    "    if mode == 'train':\n",
    "        # 训练模式下打乱数据\n",
    "        random.shuffle(index_list)\n",
    "    imgs_list = []\n",
    "    labels_list = []\n",
    "    for i in index_list:\n",
    "        # 将数据处理成希望的格式，比如类型为float32，shape为[1, 28, 28]\n",
    "        img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')\n",
    "        label = np.reshape(labels[i], [1]).astype('float32')\n",
    "        imgs_list.append(img) \n",
    "        labels_list.append(label)\n",
    "        if len(imgs_list) == BATCHSIZE:\n",
    "            # 获得一个batchsize的数据，并返回\n",
    "            yield np.array(imgs_list), np.array(labels_list)\n",
    "            # 清空数据读取列表\n",
    "            imgs_list = []\n",
    "            labels_list = []\n",
    "\n",
    "    # 如果剩余数据的数目小于BATCHSIZE，\n",
    "    # 则剩余数据一起构成一个大小为len(imgs_list)的mini-batch\n",
    "    if len(imgs_list) > 0:\n",
    "        yield np.array(imgs_list), np.array(labels_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# 3. 数据校验\n",
    "\n",
    "实际任务原始的数据可能存在数据很“脏”的情况，这里的“脏”多指数据标注不准确，或者是数据杂乱，格式不统一等等。\n",
    "\n",
    "因此，在完成数据处理函数时，我们需要执行数据校验和清理的操作。\n",
    "\n",
    "\n",
    "数据校验一般有两种方式：\n",
    "- 机器校验：加入一些校验和清理数据的操作。\n",
    "- 人工校验：先打印数据输出结果，观察是否是设置的格式。再从训练的结果验证数据处理和读取的有效性。\n",
    "\n",
    "##  机器校验\n",
    "\n",
    "如下代码所示，如果数据集中的图片数量和标签数量不等，说明数据逻辑存在问题，可使用assert语句校验图像数量和标签数据是否一致。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "    imgs_length = len(imgs)\n",
    "\n",
    "    assert len(imgs) == len(labels), \\\n",
    "          \"length of train_imgs({}) should be the same as train_labels({})\".format(len(imgs), len(labels))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## 人工校验\n",
    "\n",
    "人工校验分两步，首先打印数据输出结果，观察是否是设置的格式。再从训练的结果验证数据处理和读取的有效性。\n",
    "\n",
    "\n",
    "实现数据处理和加载函数后，我们可以调用它读取一次数据，观察数据的shape和类型是否与函数中设置的一致。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loading mnist dataset from ./work/mnist.json.gz ......\n",
      "(100, 1, 28, 28) (100, 1) <class 'numpy.ndarray'> <class 'numpy.ndarray'>\n"
     ]
    }
   ],
   "source": [
    "# 声明数据读取函数，从训练集中读取数据\n",
    "train_loader = load_data('train')\n",
    "# 以迭代的形式读取数据\n",
    "for batch_id, data in enumerate(train_loader()):\n",
    "    image_data, label_data = data\n",
    "    if batch_id == 0:\n",
    "        # 打印数据shape和类型\n",
    "        print(image_data.shape, label_data.shape, type(image_data), type(label_data))\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 观察训练结果\n",
    "\n",
    "数据处理部分后的代码多数保持不变，仅在读取数据时候调用新编写的load_data函数。由于数据格式的转换工作在load_data函数中做了一部分，所以向模型输入数据的代码变得更加简洁。下面我们使用自己实现的数据加载函数重新训练我们的神经网络。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loading mnist dataset from ./work/mnist.json.gz ......\n",
      "epoch: 0, batch: 0, loss is: [36.861202]\n",
      "epoch: 0, batch: 200, loss is: [4.169391]\n",
      "epoch: 0, batch: 400, loss is: [4.018353]\n",
      "epoch: 1, batch: 0, loss is: [4.7625484]\n",
      "epoch: 1, batch: 200, loss is: [3.3847408]\n",
      "epoch: 1, batch: 400, loss is: [3.0911984]\n",
      "epoch: 2, batch: 0, loss is: [3.6650655]\n",
      "epoch: 2, batch: 200, loss is: [3.4636102]\n",
      "epoch: 2, batch: 400, loss is: [3.4108307]\n",
      "epoch: 3, batch: 0, loss is: [4.1071653]\n",
      "epoch: 3, batch: 200, loss is: [3.3901167]\n",
      "epoch: 3, batch: 400, loss is: [3.8391578]\n",
      "epoch: 4, batch: 0, loss is: [3.2438037]\n",
      "epoch: 4, batch: 200, loss is: [2.573626]\n",
      "epoch: 4, batch: 400, loss is: [3.184749]\n",
      "epoch: 5, batch: 0, loss is: [4.2807035]\n",
      "epoch: 5, batch: 200, loss is: [4.082668]\n",
      "epoch: 5, batch: 400, loss is: [3.5914075]\n",
      "epoch: 6, batch: 0, loss is: [3.1064441]\n",
      "epoch: 6, batch: 200, loss is: [4.414713]\n",
      "epoch: 6, batch: 400, loss is: [3.244702]\n",
      "epoch: 7, batch: 0, loss is: [3.872587]\n",
      "epoch: 7, batch: 200, loss is: [3.1059146]\n",
      "epoch: 7, batch: 400, loss is: [3.5843916]\n",
      "epoch: 8, batch: 0, loss is: [2.489986]\n",
      "epoch: 8, batch: 200, loss is: [3.1297817]\n",
      "epoch: 8, batch: 400, loss is: [3.5612419]\n",
      "epoch: 9, batch: 0, loss is: [3.716842]\n",
      "epoch: 9, batch: 200, loss is: [2.5674655]\n",
      "epoch: 9, batch: 400, loss is: [4.331067]\n"
     ]
    }
   ],
   "source": [
    "#数据处理部分之后的代码，数据读取的部分调用Load_data函数\n",
    "# 定义网络结构，同上一节所使用的网络结构\n",
    "class MNIST(fluid.dygraph.Layer):\n",
    "    def __init__(self, name_scope):\n",
    "        super(MNIST, self).__init__(name_scope)\n",
    "        name_scope = self.full_name()\n",
    "        self.fc = FC(name_scope, size=1, act=None)\n",
    "\n",
    "    def forward(self, inputs):\n",
    "        outputs = self.fc(inputs)\n",
    "        return outputs\n",
    "\n",
    "# 训练配置，并启动训练过程\n",
    "with fluid.dygraph.guard():\n",
    "    model = MNIST(\"mnist\")\n",
    "    model.train()\n",
    "    #调用加载数据的函数\n",
    "    train_loader = load_data('train')\n",
    "    optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.001)\n",
    "    EPOCH_NUM = 10\n",
    "    for epoch_id in range(EPOCH_NUM):\n",
    "        for batch_id, data in enumerate(train_loader()):\n",
    "            #准备数据，变得更加简洁\n",
    "            image_data, label_data = data\n",
    "            image = fluid.dygraph.to_variable(image_data)\n",
    "            label = fluid.dygraph.to_variable(label_data)\n",
    "            \n",
    "            #前向计算的过程\n",
    "            predict = model(image)\n",
    "            \n",
    "            #计算损失，取一个批次样本损失的平均值\n",
    "            loss = fluid.layers.square_error_cost(predict, label)\n",
    "            avg_loss = fluid.layers.mean(loss)\n",
    "            \n",
    "            #每训练了200批次的数据，打印下当前Loss的情况\n",
    "            if batch_id % 200 == 0:\n",
    "                print(\"epoch: {}, batch: {}, loss is: {}\".format(epoch_id, batch_id, avg_loss.numpy()))\n",
    "            \n",
    "            #后向传播，更新参数的过程\n",
    "            avg_loss.backward()\n",
    "            optimizer.minimize(avg_loss)\n",
    "            model.clear_gradients()\n",
    "\n",
    "    #保存模型参数\n",
    "    fluid.save_dygraph(model.state_dict(), 'mnist')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "相信读到这里，您已经对如何处理数据集并实现自己的数据读取函数有了一定的了解。不妨自己尝试写一个新的数据读取函数，验证一下自己的学习效果吧。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "最后，将上述几部分操作合并到load_data函数，方便后续调用。下面代码为完整的数据读取函数，可以通过数据加载函数load_data的输入参数mode为'train', 'valid', 'eval'选择返回的数据是训练集，验证集，测试集。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "#数据处理部分的展开代码\n",
    "# 定义数据集读取器\n",
    "def load_data(mode='train'):\n",
    "\n",
    "    # 数据文件\n",
    "    datafile = './work/mnist.json.gz'\n",
    "    print('loading mnist dataset from {} ......'.format(datafile))\n",
    "    data = json.load(gzip.open(datafile))\n",
    "    # 读取到的数据可以直接区分训练集，验证集，测试集\n",
    "    train_set, val_set, eval_set = data\n",
    "\n",
    "    # 数据集相关参数，图片高度IMG_ROWS, 图片宽度IMG_COLS\n",
    "    IMG_ROWS = 28\n",
    "    IMG_COLS = 28\n",
    "    # 获得数据\n",
    "    if mode == 'train':\n",
    "        imgs = train_set[0]\n",
    "        labels = train_set[1]\n",
    "    elif mode == 'valid':\n",
    "        imgs = val_set[0]\n",
    "        labels = val_set[1]\n",
    "    elif mode == 'eval':\n",
    "        imgs = eval_set[0]\n",
    "        labels = eval_set[1]\n",
    "    else:\n",
    "        raise Exception(\"mode can only be one of ['train', 'valid', 'eval']\")\n",
    "\n",
    "    imgs_length = len(imgs)\n",
    "\n",
    "    assert len(imgs) == len(labels), \\\n",
    "          \"length of train_imgs({}) should be the same as train_labels({})\".format(\n",
    "                  len(imgs), len(labels))\n",
    "\n",
    "    index_list = list(range(imgs_length))\n",
    "\n",
    "    # 读入数据时用到的batchsize\n",
    "    BATCHSIZE = 100\n",
    "\n",
    "    # 定义数据生成器\n",
    "    def data_generator():\n",
    "        if mode == 'train':\n",
    "            # 训练模式下，将训练数据打乱\n",
    "            random.shuffle(index_list)\n",
    "        imgs_list = []\n",
    "        labels_list = []\n",
    "        \n",
    "        for i in index_list:\n",
    "            img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')\n",
    "            label = np.reshape(labels[i], [1]).astype('float32')\n",
    "            imgs_list.append(img) \n",
    "            labels_list.append(label)\n",
    "            if len(imgs_list) == BATCHSIZE:\n",
    "                # 产生一个batch的数据并返回\n",
    "                yield np.array(imgs_list), np.array(labels_list)\n",
    "                # 清空数据读取列表\n",
    "                imgs_list = []\n",
    "                labels_list = []\n",
    "\n",
    "        # 如果剩余数据的数目小于BATCHSIZE，\n",
    "        # 则剩余数据一起构成一个大小为len(imgs_list)的mini-batch\n",
    "        if len(imgs_list) > 0:\n",
    "            yield np.array(imgs_list), np.array(labels_list)\n",
    "    return data_generator"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "\n",
    "# 4. 异步数据读取\n",
    "\n",
    "\n",
    "上面提到的数据读取是同步数据读取方式，针对于样本量较大、数据读取较慢的场景，建议采用异步数据读取方式，可以让数据读取和模型训练并行化，加快数据读取速度，牺牲一小部分内存换取数据读取效率的提升。\n",
    "\n",
    "\n",
    "------\n",
    "**说明：**\n",
    "\n",
    "- 同步数据读取：每当模型需要数据的时候，运行数据读取函数获得当前批次的数据。在读取数据期间，模型一直在等待数据读取结束，获得数据后才会进行计算。\n",
    "- 异步数据读取：数据读取和模型训练过程异步进行，读取到的数据先放入缓存区。模型训练完一个批次后，不用等待数据读取过程，直接从缓存区获得下一批次数据进行训练。\n",
    "\n",
    "------\n",
    "\n",
    "使用飞桨实现异步数据读取非常简单，代码如下所示。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loading mnist dataset from ./work/mnist.json.gz ......\n",
      "0 [100, 1, 28, 28] [100, 1]\n",
      "1 [100, 1, 28, 28] [100, 1]\n",
      "2 [100, 1, 28, 28] [100, 1]\n",
      "3 [100, 1, 28, 28] [100, 1]\n",
      "4 [100, 1, 28, 28] [100, 1]\n",
      "5 [100, 1, 28, 28] [100, 1]\n"
     ]
    }
   ],
   "source": [
    "# 定义数据读取后存放的位置，CPU或者GPU，这里使用CPU\n",
    "# place = fluid.CUDAPlace(0) 时，数据读到GPU上\n",
    "place = fluid.CPUPlace()\n",
    "with fluid.dygraph.guard(place):\n",
    "    # 声明数据加载函数，使用训练模式\n",
    "    train_loader = load_data(mode='train')\n",
    "    # 定义DataLoader对象用于加载Python生成器产生的数据\n",
    "    data_loader = fluid.io.DataLoader.from_generator(capacity=5, return_list=True)\n",
    "    # 设置数据生成器\n",
    "    data_loader.set_batch_generator(train_loader, places=place)\n",
    "    # 迭代的读取数据并打印数据的形状\n",
    "    for i, data in enumerate(data_loader):\n",
    "        image_data, label_data = data\n",
    "        print(i, image_data.shape, label_data.shape)\n",
    "        if i>=5:\n",
    "            break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "与同步数据读取相比，异步数据读取仅增加了三行代码，如下所示。\n",
    "\n",
    "```python\n",
    "place = fluid.CPUPlace() \n",
    "data_loader = fluid.io.DataLoader.from_generator(capacity=5, return_list=True)\n",
    "data_loader.set_batch_generator(train_loader, place)\n",
    "```\n",
    "我们展开解读一下：\n",
    "\n",
    "* **第一行代码：** 设置读取的数据是放在CPU还是GPU上。\n",
    "* **第二行代码：** 创建一个DataLoader对象用于加载Python生成器产生的数据。数据会由Python线程预先读取，并异步送入一个队列中。fluid.io.DataLoader.from_generator参数名称、参数含义、默认值如下：\n",
    "\n",
    "参数名和默认值如下：\n",
    "* feed_list=None, \n",
    "* capacity=None, \n",
    "* use_double_buffer=True,\n",
    "* iterable=True,\n",
    "* return_list=False\n",
    "\n",
    "参数含义如下：\n",
    "\n",
    "- feed_list        仅在paddle静态图中使用，动态图中设置为None，本教程默认使用动态图的建模方式。\n",
    "- capacity        表示在DataLoader中维护的队列容量，如果读取数据的速度很快，建议设置为更大的值。\n",
    "- use_double_buffer   是一个布尔型的参数，设置为True时Dataloader会预先异步读取下一个batch的数据放到缓存区。\n",
    "- iterable          表示创建的Dataloader对象是否是可迭代的，一般设置为True。\n",
    "- return_list        在动态图下需要设置为True。\n",
    "\n",
    "* **第三行代码：** 用创建的DataLoader对象设置一个数据生成器set_batch_generator，输入的参数是一个Python数据生成器train_loader和服务器资源类型place（标明CPU还是GPU）。\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "异步数据读取并训练的完整案例代码如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loading mnist dataset from ./work/mnist.json.gz ......\n",
      "epoch: 0, batch: 0, loss is: [47.428585]\n",
      "epoch: 0, batch: 200, loss is: [4.3324847]\n",
      "epoch: 0, batch: 400, loss is: [4.013606]\n",
      "epoch: 1, batch: 0, loss is: [4.2797394]\n",
      "epoch: 1, batch: 200, loss is: [3.1092007]\n",
      "epoch: 1, batch: 400, loss is: [3.254475]\n",
      "epoch: 2, batch: 0, loss is: [3.7360413]\n",
      "epoch: 2, batch: 200, loss is: [3.185952]\n",
      "epoch: 2, batch: 400, loss is: [3.7419617]\n"
     ]
    }
   ],
   "source": [
    "with fluid.dygraph.guard():\n",
    "    model = MNIST(\"mnist\")\n",
    "    model.train()\n",
    "    #调用加载数据的函数\n",
    "    train_loader = load_data('train')\n",
    "    # 创建异步数据读取器\n",
    "    place = fluid.CPUPlace()\n",
    "    data_loader = fluid.io.DataLoader.from_generator(capacity=5, return_list=True)\n",
    "    data_loader.set_batch_generator(train_loader, places=place)\n",
    "    \n",
    "    optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.001)\n",
    "    EPOCH_NUM = 3\n",
    "    for epoch_id in range(EPOCH_NUM):\n",
    "        for batch_id, data in enumerate(data_loader):\n",
    "            image_data, label_data = data\n",
    "            image = fluid.dygraph.to_variable(image_data)\n",
    "            label = fluid.dygraph.to_variable(label_data)\n",
    "            \n",
    "            predict = model(image)\n",
    "            \n",
    "            loss = fluid.layers.square_error_cost(predict, label)\n",
    "            avg_loss = fluid.layers.mean(loss)\n",
    "            \n",
    "            if batch_id % 200 == 0:\n",
    "                print(\"epoch: {}, batch: {}, loss is: {}\".format(epoch_id, batch_id, avg_loss.numpy()))\n",
    "            \n",
    "            avg_loss.backward()\n",
    "            optimizer.minimize(avg_loss)\n",
    "            model.clear_gradients()\n",
    "\n",
    "    fluid.save_dygraph(model.state_dict(), 'mnist')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "从异步数据读取的训练结果来看，损失函数下降与同步数据读取训练结果基本一致。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "PaddlePaddle 1.6.0 (Python 3.5)",
   "language": "python",
   "name": "py35-paddle1.2.0"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
